"""游戏的入口"""
import sys
import pygame
from my_plane import MyPlane
from bullet import Bullet
from small_enemy import SmallEnemy
import constants
from mid_enemy import MidEnemy
from big_enemy import BigEnemy
from pygame.sprite import Group
from double_bullet_supply import DoubleBulletSupply
from double_bullet import DoubleBullet


class PlaneWar:
    """游戏类"""
    def __init__(self):
        # 【1】初始化pygame库
        pygame.init()
        # 【2】初始化窗口
        self._init_window()
        self._set_window()
        # 创建我方飞机的实例
        self.my_plane = MyPlane(self.window)
        self._create_groups()
        # 创建一个用于跟踪时间的时钟对象
        self.clock = pygame.time.Clock()
        self._create_timers()
        # 是否游戏结束
        self.is_game_over = False
        # 获取字体资源
        self._get_fonts()
        # 双发子弹计数器
        self.double_bullet_counter = 0

    def run_game(self):
        """运行游戏"""
        # 【3】创建循环让窗口一直显示
        while True:
            self._handle_events()
            # 设置while循环体在【一秒】内执行的【最大次数】（设置动画的最大帧数）
            self.clock.tick(constants.MAX_FRAME_RATE)
            # 设置窗口的颜色
            self.window.fill(constants.BACKGROUND_BG)
            if not self.is_game_over:
                # 检测碰撞
                self._check_collisions()
            # 处理窗口中的元素
            self._draw_elements()
            if not self.is_game_over:
                self._update_elements()
                self._delete_invisible_elements()
                # 开始播放动画
                self._start_animations()
            # 将内存中的窗口对象绘制到屏幕上更新屏幕
            pygame.display.flip()

    def _init_window(self):
        """初始化窗口"""
        screen_width, screen_height = self.get_screen_size()
        window_width, window_height = (screen_width * constants.SCALE_HORIZONTAL,
                                       screen_height * constants.SCALE_VERTICAL)
        self.window = pygame.display.set_mode((round(window_width), round(window_height)))

    @staticmethod
    def _set_window():
        """设置窗口"""
        pygame.display.set_caption("飞机大战")
        window_icon = pygame.image.load("images/my_plane1.png")
        pygame.display.set_icon(window_icon)

    def _create_groups(self):
        # 创建一个子弹容器
        self.bullet_group = Group()
        # 创建一个小型敌机容器
        self.small_enemy_group = Group()
        # 创建一个中型敌机容器
        self.mid_enemy_group = Group()
        # 创建一个大型敌机容器
        self.big_enemy_group = Group()
        # 创建一个所有敌机的容器
        self.enemy_group = Group()
        # 创建一个双发子弹补给的容器
        self.double_bullet_supply_group = Group()
        # 创建一个双发子弹的容器
        self.double_bullet_group = Group()

    @staticmethod
    def _create_timers():
        """定时创建自定义事件"""
        pygame.time.set_timer(constants.ID_OF_CREATE_BULLET, constants.INTERVAL_OF_CREATE_BULLET)
        pygame.time.set_timer(constants.ID_OF_CREATE_SMALL_ENEMY, constants.INTERVAL_OF_CREATE_SMALL_ENEMY)
        pygame.time.set_timer(constants.ID_OF_CREATE_MID_ENEMY, constants.INTERVAL_OF_CREATE_MID_ENEMY)
        pygame.time.set_timer(constants.ID_OF_CREATE_BIG_ENEMY, constants.INTERVAL_OF_CREATE_BIG_ENEMY)
        pygame.time.set_timer(constants.ID_OF_DOUBLE_BULLET_SUPPLY, constants.INTERVAL_OF_DOUBLE_BULLET_SUPPLY)

    @staticmethod
    def _cancel_timers():
        """取消自定义事件定时器"""
        pygame.time.set_timer(constants.ID_OF_CREATE_BULLET, 0)
        pygame.time.set_timer(constants.ID_OF_CREATE_SMALL_ENEMY, 0)
        pygame.time.set_timer(constants.ID_OF_CREATE_MID_ENEMY, 0)
        pygame.time.set_timer(constants.ID_OF_CREATE_BIG_ENEMY, 0)
        pygame.time.set_timer(constants.ID_OF_DOUBLE_BULLET_SUPPLY, 0)

    def _get_fonts(self):
        """获取到字体资源"""
        self.font_32 = pygame.font.Font("fonts/wawa.ttf", constants.FONT_SIZE_OF_32)
        self.font_100 = pygame.font.Font("fonts/wawa.ttf", constants.FONT_SIZE_OF_100)

    def _handle_events(self):
        """处理事件，从事件队列中将【所有事件】取出并逐个处理"""
        for event in pygame.event.get():
            # 如果事件是退出程序
            if event.type == pygame.QUIT:
                # 卸载pygame库
                pygame.quit()
                # 退出程序
                sys.exit()

            # 键盘按钮事件
            elif event.type == pygame.KEYDOWN or event.type == pygame.KEYUP:
                if event.key == pygame.K_UP or event.key == pygame.K_w:
                    self.my_plane.is_move_up = event.type == pygame.KEYDOWN
                elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
                    self.my_plane.is_move_down = event.type == pygame.KEYDOWN
                elif event.key == pygame.K_LEFT or event.key == pygame.K_a:
                    self.my_plane.is_move_Left = event.type == pygame.KEYDOWN
                elif event.key == pygame.K_RIGHT or event.key == pygame.K_d:
                    self.my_plane.is_move_Right = event.type == pygame.KEYDOWN

            # 处理自定义“创建子弹”事件
            elif event.type == constants.ID_OF_CREATE_BULLET:
                self._create_bullet()

            # 处理自定义“创建小型敌机”事件
            elif event.type == constants.ID_OF_CREATE_SMALL_ENEMY:
                # 创建一个小型敌机
                small_enemy = SmallEnemy(self.window)
                self.small_enemy_group.add(small_enemy)  # type:ignore
                self.enemy_group.add(small_enemy)  # type:ignore

            # 处理自定义“创建中型敌机”事件
            elif event.type == constants.ID_OF_CREATE_MID_ENEMY:
                # 创建一个中型敌机
                mid_enemy = MidEnemy(self.window)
                self.mid_enemy_group.add(mid_enemy)  # type:ignore
                self.enemy_group.add(mid_enemy)  # type:ignore

            # 处理自定义“创建大型敌机”事件
            elif event.type == constants.ID_OF_CREATE_BIG_ENEMY:
                # 创建一个大型敌机
                big_enemy = BigEnemy(self.window)
                self.big_enemy_group.add(big_enemy)  # type:ignore
                self.enemy_group.add(big_enemy)  # type:ignore

            # 处理自定义“取消我方飞机无敌”事件
            elif event.type == constants.ID_OF_CANCEL_INVINCIBLE:
                self.my_plane.is_invincible = False
                pygame.time.set_timer(constants.ID_OF_CANCEL_INVINCIBLE, 0)

            # 处理自定义“双发子弹补给”事件
            elif event.type == constants.ID_OF_DOUBLE_BULLET_SUPPLY:
                double_bullet_supply = DoubleBulletSupply(self.window)
                self.double_bullet_supply_group.add(double_bullet_supply)  # type:ignore

            # 处理自定义“双发子弹”事件
            elif event.type == constants.ID_OF_CREATE_DOUBLE_BULLET:
                self.double_bullet_counter += 1
                if self.double_bullet_counter <= constants.DOUBLE_BULLET_MAX_COUNTER:
                    self._create_double_bullet()
                else:
                    # 停止双发子弹事件，开始普通子弹事件
                    pygame.time.set_timer(constants.ID_OF_CREATE_DOUBLE_BULLET, 0)
                    pygame.time.set_timer(constants.ID_OF_CREATE_BULLET, constants.INTERVAL_OF_CREATE_BULLET)
                    # 如果当前是双发子弹消失的时刻，双发子弹消失时先发射一颗普通子弹
                    self._create_bullet()
                    # 重置计数器
                    self.double_bullet_counter = 0

    def _create_bullet(self):
        # 创建一颗普通子弹
        bullet = Bullet(self.window, self.my_plane)
        self.bullet_group.add(bullet)  # type:ignore

    def _create_double_bullet(self):
        """创建双发子弹"""
        double_bullet_left = DoubleBullet(self.window)
        double_bullet_right = DoubleBullet(self.window)
        # 设置双发子弹的左边子弹偏移量
        double_bullet_left.rect.center = (self.my_plane.rect.centerx - constants.DOUBLE_BULLET_HORIZONTAL_OFFSET,
                                          self.my_plane.rect.centery)
        # 设置双发子弹的右边子弹偏移量
        double_bullet_right.rect.center = (self.my_plane.rect.centerx + constants.DOUBLE_BULLET_HORIZONTAL_OFFSET,
                                           self.my_plane.rect.centery)
        self.double_bullet_group.add(double_bullet_left)  # type:ignore
        self.double_bullet_group.add(double_bullet_right)  # type:ignore

    def _check_collisions(self):
        """碰撞检测"""
        # 检测子弹与小型敌机碰撞
        self._check_collision_bullets_smalls(self.bullet_group)
        # 检测双发子弹与小型敌机碰撞
        self._check_collision_bullets_smalls(self.double_bullet_group)
        # 检测子弹与中型和大型敌机碰撞
        self._check_collision_bullets_mid_or_big(self.mid_enemy_group)
        self._check_collision_bullets_mid_or_big(self.big_enemy_group)
        # 检测我方飞机和敌机的碰撞
        self._check_collision_plane_enemies()
        # 检测我方飞机和补给的碰撞
        self._check_collision_plane_supply()

    def _draw_elements(self):
        """绘制画面元素"""
        self.my_plane.draw()
        # 调用Group中的draw(window)方法，Group里面每个元素会调用self.window.blit(self.image, self.rect)
        self.bullet_group.draw(self.window)
        self.small_enemy_group.draw(self.window)
        self.mid_enemy_group.draw(self.window)
        self.big_enemy_group.draw(self.window)
        self.double_bullet_supply_group.draw(self.window)
        self.double_bullet_group.draw(self.window)
        # 画出敌机的生命线
        for mid_enemy in self.mid_enemy_group.sprites():
            mid_enemy.draw_life_line()
        for big_enemy in self.big_enemy_group.sprites():
            big_enemy.draw_life_line()
        # 画出生命值飞机
        for i in range(0, self.my_plane.life):
            self.window.blit(self.my_plane.image_life, self.my_plane.life_rect_list[i])
        # 我方飞机是否是无敌状态
        if self.my_plane.is_invincible:
            self._draw_invincible_prompt()
        # 是否游戏结束
        if self.is_game_over:
            self._draw_game_over_prompt()

    def _update_elements(self):
        """更新画面元素"""
        self.my_plane.update()
        # 调用Group中的update()方法，Group里面每个元素会调用它自己的update()方法
        self.bullet_group.update()
        self.small_enemy_group.update()
        self.mid_enemy_group.update()
        self.big_enemy_group.update()
        self.double_bullet_supply_group.update()
        self.double_bullet_group.update()

    def _delete_invisible_elements(self):
        """删除窗口不可见的元素"""
        self.delete_invisible_bullet(self.bullet_group)
        self.delete_invisible_bullet(self.double_bullet_group)
        self.delete_invisible_enemy_or_supply(self.enemy_group)
        self.delete_invisible_enemy_or_supply(self.double_bullet_supply_group)

    def _start_animations(self):
        """开始播放动画"""
        # 我方飞机开始动画
        self.my_plane.start_animation()
        # 大型敌机开始动画
        for big_enemy in self.big_enemy_group.sprites():
            big_enemy.start_animation()

    @staticmethod
    def delete_invisible_bullet(group):
        """删除窗口中不可见的子弹"""
        for bullet in group.sprites():
            if bullet.rect.bottom <= 0:
                group.remove(bullet)

    def delete_invisible_enemy_or_supply(self, group):
        """删除窗口中不可见的敌机或者补给"""
        for sprite in group.sprites():
            if sprite.rect.top >= self.window.get_rect().bottom:
                # 删除所有group中的对象
                sprite.kill()

    def _draw_invincible_prompt(self):
        """绘制我方飞机无敌时的提示文字"""
        prompt_text = "剩余{}生命值，无敌状态会在当前文字消失时解除".format(self.my_plane.life)
        # 获得文字的surface
        prompt_text_surface = self.font_32.render(prompt_text, True, (255, 255, 255))
        # 获得文字的矩形
        prompt_text_rect = prompt_text_surface.get_rect()
        # 设置文字在窗口中居中
        prompt_text_rect.center = self.window.get_rect().center
        # 绘制
        self.window.blit(prompt_text_surface, prompt_text_rect)

    def _draw_game_over_prompt(self):
        """绘制游戏结束时的提示文字"""
        prompt_text = "游戏结束"
        # 获得文字的surface
        prompt_text_surface = self.font_100.render(prompt_text, True, (255, 255, 255))
        # 获得文字的矩形
        prompt_text_rect = prompt_text_surface.get_rect()
        # 设置文字在窗口中居中
        prompt_text_rect.center = self.window.get_rect().center
        # 绘制
        self.window.blit(prompt_text_surface, prompt_text_rect)

    def _check_collision_bullets_smalls(self, group):
        """检测子弹或者双发与小型敌机碰撞"""
        # 检测是否有子弹和小型敌机碰撞，返回一个子典，字典的key是groupA中发送碰撞的精灵，字典的
        # value是groupB中与对应key发生碰撞的所有精灵组成的列表
        dict_collided = pygame.sprite.groupcollide(self.small_enemy_group, group, False, True)
        # 判断是否发生了碰撞
        if len(dict_collided) > 0:
            for small_enemy in dict_collided.keys():
                # 判断是否已经在播放爆炸动画
                if not small_enemy.is_playing_explode_animation:
                    # 播放爆炸声音
                    small_enemy.play_explode_sound()
                    # 修改标识位为True, 既开始播放爆炸动画
                    small_enemy.is_playing_explode_animation = True

        # 遍历小型敌机group中的标识位是否为True, 是则播放爆炸动画
        for small_enemy in self.small_enemy_group:
            if small_enemy.is_playing_explode_animation:
                small_enemy.start_explode_animation()

    def _check_collision_bullets_mid_or_big(self, mid_or_big_enemy_group):
        """检测子弹与敌机碰撞"""
        # 检测是否有子弹和敌机碰撞，返回一个子典，字典的key是groupA中发送碰撞的精灵，字典的
        # value是groupB中与对应key发生碰撞的所有精灵组成的列表
        dict_collided = pygame.sprite.groupcollide(mid_or_big_enemy_group, self.bullet_group, False, True)
        # 判断是否发生了碰撞
        if len(dict_collided) > 0:
            for enemy in dict_collided.keys():
                enemy.life -= 1
                if enemy.life > 0:
                    if not enemy.is_playing_hit_animation:
                        # 修改标识位为True, 既开始播放被击中动画
                        enemy.is_playing_hit_animation = True
                elif enemy.life == 0:
                    # 判断是否已经在播放爆炸动画
                    if not enemy.is_playing_explode_animation:
                        # 播放爆炸声音
                        enemy.play_explode_sound()
                        # 修改标识位为True, 既开始播放爆炸动画
                        enemy.is_playing_explode_animation = True

        # 遍历敌机group中的标识位是否为True, 是则播放对应动画
        for enemy in mid_or_big_enemy_group:
            if enemy.is_playing_hit_animation:
                enemy.start_hit_animation()
            if enemy.is_playing_explode_animation:
                enemy.start_explode_animation()

    def _check_collision_plane_enemies(self):
        """检测我方飞机和敌机的碰撞"""
        # pygame.sprite.collide_mask,让2个精灵的可见部分碰撞才算2个精灵碰撞了
        collided_list = pygame.sprite.spritecollide(self.my_plane, self.enemy_group,
                                                    False, pygame.sprite.collide_mask)  # type:ignore
        # 如果发生碰撞
        if len(collided_list) > 0:
            if not self.my_plane.is_invincible:
                self.my_plane.life -= 1
                if self.my_plane.life > 0:
                    # 重置我方飞机位置
                    self.my_plane.reset_position()
                    # 让我方飞机无敌
                    self.my_plane.is_invincible = True
                    # 添加一个取消我方飞机无敌的事件
                    pygame.time.set_timer(constants.ID_OF_CANCEL_INVINCIBLE, constants.INTERVAL_OF_CANCEL_INVINCIBLE)
                    # 重置子弹
                    pygame.time.set_timer(constants.ID_OF_CREATE_DOUBLE_BULLET, 0)
                    pygame.time.set_timer(constants.ID_OF_CREATE_BULLET, constants.INTERVAL_OF_CREATE_BULLET)
                    self.double_bullet_counter = 0
                elif self.my_plane.life == 0:
                    # 取消定时器
                    self._cancel_timers()
                    # 标记游戏结束
                    self.is_game_over = True
            # 添加敌机被我方飞机碰撞时的爆炸音效和爆炸动画
            for enemy in collided_list:
                if not enemy.is_playing_explode_animation:
                    enemy.play_explode_sound()
                    enemy.is_playing_explode_animation = True
            for enemy in self.enemy_group:
                if enemy.is_playing_explode_animation:
                    enemy.start_explode_animation()

    def _check_collision_plane_supply(self):
        """检测我方飞机和弹药补给的碰撞"""
        # pygame.sprite.collide_mask,让2个精灵的可见部分碰撞才算2个精灵碰撞了
        collided_list = pygame.sprite.spritecollide(self.my_plane, self.double_bullet_supply_group,
                                                    True, pygame.sprite.collide_mask)  # type:ignore
        # 如果发生碰撞
        if len(collided_list) > 0:
            # 添加我方飞机与补给碰撞时的音效
            for supply in collided_list:
                supply.play_get_supply_sound()
            # 停止普通子弹的定时器
            pygame.time.set_timer(constants.ID_OF_CREATE_BULLET, 0)
            # 开始双发子弹的定时器
            pygame.time.set_timer(constants.ID_OF_CREATE_DOUBLE_BULLET, constants.INTERVAL_OF_CREATE_DOUBLE_BULLET)
            # 如果当前不是双发子弹，获得双发子弹的补给时先发射一颗双发子弹
            if self.double_bullet_counter == 0:
                self._create_double_bullet()
            # 获取到双发子弹就把之前的子弹计数器重置
            self.double_bullet_counter = 0

    @staticmethod
    def get_screen_size():
        """获取当前屏幕的尺寸"""
        info = pygame.display.Info()
        screen_width = info.current_w
        screen_height = info.current_h
        return screen_width, screen_height


if __name__ == '__main__':
    # 运行游戏
    PlaneWar().run_game()
